// 注意:
// 使用tokio系列API创建的socket, 会自动被设置SO_NONBLOCK
// 而使用fd转换成的tokio Socket/Stream/Listener, 不会被自行设置SO_NONBLOCK.
// 详细见测试内容.

use std::mem;

#[allow(dead_code)]
pub fn get_fcntl(socket_fd: i32) -> i32 {
    unsafe { libc::fcntl(socket_fd, libc::F_GETFL, 0) }
}

#[allow(dead_code)]
pub fn set_fcntl(socket_fd: i32, flag: i32) -> bool {
    unsafe {
        let res = libc::fcntl(socket_fd, libc::F_SETFL, flag);
        if res < 0 {
            return false;
        }
        true
    }
}

#[allow(dead_code)]
pub fn set_nonblock(socket_fd: i32) -> bool {
    let mut flag = get_fcntl(socket_fd);
    if flag < 0 {
        return false;
    }

    flag |= libc::O_NONBLOCK;
    set_fcntl(socket_fd, flag)
}

#[allow(dead_code)]
pub fn set_ipv6only(socket_fd: i32) -> bool {
    unsafe {
        let mut value: i32 = 1;
        let res = libc::setsockopt(
            socket_fd,
            libc::IPPROTO_IPV6,
            libc::IPV6_V6ONLY,
            &mut value as *mut i32 as *const _,
            std::mem::size_of::<i32>() as _,
        );

        if res == -1 {
            return false;
        }

        true
    }
}

#[allow(dead_code)]
pub fn socket_alive(socket_fd: i32) -> bool {
    let mut error: libc::c_int = 0;
    let mut error_len: libc::socklen_t =
        mem::size_of::<libc::c_int>() as libc::socklen_t;

    unsafe {
        let res = libc::getsockopt(
            socket_fd,
            libc::SOL_SOCKET,
            libc::SO_ERROR,
            (&mut error as *mut libc::c_int) as *mut libc::c_void,
            &mut error_len as *mut libc::socklen_t,
        );

        if res == -1 {
            return false;
        }

        if error != 0 {
            return false;
        }
    }

    true
}

// znx_bool_t
// znx_session_conn_avalive_check(znx_conn_t *conn)
// {
//     int         err = 0;
//     socklen_t   len = 0;

//     if (getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, (void *)&err, &len) == -1) {
//         return ZNX_FALSE;
//     }

//     if (err) {
//         conn->read.error = 1;
//         conn->write.error = 1;
//         return ZNX_FALSE;
//     }

//     return ZNX_TRUE;
// }

#[cfg(test)]
mod tests {
    use std::os::fd::{AsFd, AsRawFd, FromRawFd};

    use tokio::net::TcpSocket;

    use super::get_fcntl;

    #[test]
    fn test_fd_to_tokio_socket_nonblocking() {
        // 使用libc创建的socket fd初始化的Tokio::TcpSocket不会自动设置SO_NONBLOCK.
        unsafe {
            let socket_fd = libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0);
            let res = get_fcntl(socket_fd);

            let nonblock_res = res | libc::O_NONBLOCK;
            assert_ne!(nonblock_res, res);

            let socket = TcpSocket::from_raw_fd(res);
            let tokio_socket_fd = socket.as_fd().as_raw_fd();
            assert_ne!(nonblock_res, tokio_socket_fd);

            let nonblocl_tokio_socket_fd = tokio_socket_fd | libc::O_NONBLOCK;
            assert_eq!(nonblocl_tokio_socket_fd, nonblock_res);
        }
    }

    #[test]
    fn test_tokio_socket_nonbloking() {
        let socket = TcpSocket::new_v4().unwrap();
        let socket_fd = socket.as_raw_fd();
        let res = get_fcntl(socket_fd);
        let nonblock_res = res | libc::O_NONBLOCK;
        assert_eq!(res, nonblock_res);
    }
}
